package com.yuneec.droneservice.protocol.analysis;

import android.util.SparseArray;

import com.yuneec.droneservice.tools.RCLog;
import com.yuneec.droneservice.utils.CrcUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public abstract class Analysis implements IAnalysis {///////////////////////////////////////////////////////////////////
    private ExecutorService handlerService;
    private SparseArray<MessageHandler> handlerQueue;
    private LinkedBlockingQueue<Message> messages;
    private HandleException exceptionHandler;
    private byte[] lock = new byte[0];
    private InputStream inputStream;
    private OutputStream outputStream;

    private volatile boolean isStart = false;

    public Analysis() {
        super();
        handlerQueue = new SparseArray<MessageHandler>(20);
        messages = new LinkedBlockingQueue<Message>(30);
        for (int i = 0; i < 20; i++) {
            messages.offer(new Message(messages));
        }
    }

    protected final MessageHandler getMessageHandler(int id) {
        return handlerQueue.get(id);
    }

    protected final Message obtainMessage() {
        Message message = null;
        try {
            message = messages.poll();
        } catch (Exception e) {
        }
        if (message == null) {
            message = new Message(messages);
        }
        return message;
    }

    public final static class Message {
        private int length;
        private int offset;
        private int id;
        private byte retain;
        private byte[] buf;
        private WeakReference<BlockingQueue<Message>> reference;
        private WeakReference<MessageHandler> handlerReference;
        public Throwable throwable;

        public Message(BlockingQueue<Message> queue) {
            reference = new WeakReference<BlockingQueue<Message>>(queue);
        }

        public void recycle() {
            throwable = null;
            Queue<Message> cache = null;
            if (reference != null && (cache = reference.get()) != null) {
                try {
                    cache.offer(this);
                } catch (Exception e) {
                    buf = null;
                }
            }
        }

        public void init(int id, int length, byte retain) {
            this.id = id;
            this.length = length;
            this.retain = retain;
            if (this.buf == null || this.buf.length < length) {
                this.buf = new byte[length];
            }
            this.offset = 0;
        }

        public int ids() {
            return id;
        }

        public int offset() {
            return offset;
        }

        public void offset(int off) {
            this.offset = off;
        }

        public int length() {
            return this.length;
        }

        public void length(int len) {
            this.length = len;
        }

        public byte[] body() {
            return buf;
        }

        public byte retain() {
            return retain;
        }

        public void setTarget(MessageHandler handler) {
            if (handler != null) {
                handlerReference = new WeakReference<MessageHandler>(handler);
            } else {
                handlerReference = null;
            }
        }

        public void sendToTarget() {
            if (handlerReference != null) {
                MessageHandler handler = handlerReference.get();
                if (handler != null) {
                    handler.handle(this);
                }
            }
        }
    }

    public final static class MessageHandler implements Runnable {
        private final BlockingQueue<Message> messageQueue;
        private static final int MESSAGE_TIME_OUT = 5000;
        private final IMessageReceiver receiver;
        private boolean isHandlerRunning = false;

        public boolean eq(IMessageReceiver receiver) {
            return this.receiver == receiver;
        }

        public MessageHandler(IMessageReceiver receiver) {
            messageQueue = new LinkedBlockingQueue<Message>(30);
            if (receiver == null) {
                throw new NullPointerException();
            }
            this.receiver = receiver;
        }

        @Override
        public final void run() {
            while (isHandlerRunning) {
                Message message = null;
                try {
                    try {
                        message = messageQueue.poll(MESSAGE_TIME_OUT, TimeUnit.MILLISECONDS);
                    } catch (Exception e) {
                    }
                    if (message != null) {
                        if (message.throwable != null) {
                            receiver.catchException(message.throwable);
                        } else {
                            receiver.handleMessage(message);
                        }
                    }
                } catch (Exception e) {
                    receiver.catchException(e);
                } finally {
                    if (message != null) {
                        message.recycle();
                    }
                }
            }
            Message message = null;
            while ((message = messageQueue.poll()) != null) {
                receiver.handleMessage(message);
                message.recycle();
            }
            messageQueue.clear();
        }

        public void start(ExecutorService service) {
            if (!isHandlerRunning) {
                synchronized (this) {
                    if (!isHandlerRunning) {
                        isHandlerRunning = true;
                        service.execute(this);
                    }
                }
            }
        }

        public void cancel() {
            if (isHandlerRunning) {
                synchronized (this) {
                    if (isHandlerRunning) {
                        isHandlerRunning = false;
                        receiver.recycle();
                    }
                }
            }
        }

        public void handle(Message message) {
            if (message == null) {
                return;
            }
            if (isHandlerRunning) {
                try {
                    // messageQueue.offer(message, 2, TimeUnit.SECONDS);
                    messageQueue.put(message);
                } catch (InterruptedException e) {
                    RCLog.d("message into Queue error :" + e.getMessage());
                    message.recycle();
                }
            }
        }

    }

    @Override
    public final void start(final OutputStream os) {
        if (isStart) {
            return;
        }
        synchronized (lock) {
            if (isStart) {
                return;
            }
            isStart = true;
            outputStream = os;
            if (handlerService != null && !handlerService.isShutdown()) {
                handlerService.shutdownNow();
            }
            handlerService = Executors.newCachedThreadPool();

            for (int index = 0; index < handlerQueue.size(); index++) {
                int id = handlerQueue.keyAt(index);
                MessageHandler handler = handlerQueue.get(id);
                if (handler != null) {
                    handler.start(handlerService);
                }
            }
            onStart();
        }

    }

    protected void onStart() {
    }

    public final void stop() {

        if (isStart) {
            synchronized (lock) {
                if (!isStart) {
                    return;
                }
                isStart = false;
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        if (exceptionHandler != null) {
                            exceptionHandler.catchException(e);
                        }
                    }
                }
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        if (exceptionHandler != null) {
                            exceptionHandler.catchException(e);
                        }
                    }
                }
                inputStream = null;
                outputStream = null;

                for (int index = 0; index < handlerQueue.size(); index++) {
                    int id = handlerQueue.keyAt(index);
                    MessageHandler handler = handlerQueue.get(id);
                    if (handler != null) {
                        handler.cancel();
                    }
                }

                if (handlerService != null && !handlerService.isShutdown()) {
                    handlerService.shutdownNow();
                }
                handlerService = null;

            }
        }
    }

    public final void registMessageHandler(final IMessageReceiver receiver) {
        if (receiver == null) {
            return;
        }
        synchronized (lock) {
            int id = receiver.getMessageId();
            RCLog.d("Msg ID:" + id);
            MessageHandler handler = handlerQueue.get(id);
            if (handler != null) {
                if (handler.receiver == receiver) {
                    return;
                }
                handlerQueue.remove(id);
                handler.cancel();
            }
            handler = new MessageHandler(receiver);
            if (isStart) {
                handler.start(handlerService);
            }
            handlerQueue.put(id, handler);
        }
    }

    public final void unRegistMessageHandler(final int id) {
        synchronized (lock) {
            MessageHandler handler = handlerQueue.get(id);
            if (handler != null) {
                handlerQueue.remove(id);
                handler.cancel();
            }
        }
    }

    public abstract void clear();

    @Override
    public void throwException(Throwable e, boolean clear) {
        if (exceptionHandler != null) {
            exceptionHandler.catchException(e);
        }
        if (clear) {
            clear();
        }
    }

    public final void asyncSend(int id, byte[] buf) {
        asyncSend(id, buf, 0, buf.length);
    }

    public final void asyncSend(int id, byte[] buf, int off, int len) {
        if (handlerService == null || handlerService.isShutdown()) {
            return;
        }
        handlerService.execute(new Sender(this, id, buf, off, len));
    }

    public int send(int id, byte[] buf) {
        return send(id, buf, 0, buf.length);
    }

    public int send(int id, byte[] buf, int off, int len) {
        try {
            if (handlerService == null || handlerService.isShutdown()) {
                return -1;
            }
            Callable<Integer> caller = new Sender(this, id, buf, off, len);
            return handlerService.submit(caller).get();
        } catch (Exception e) {
            if (exceptionHandler != null) {
                exceptionHandler.catchException(e);
            }
            return -1;
        }
    }

    private static long AOA_NO = 0;

    public static class Sender implements Runnable, Callable<Integer> {
        private static final byte[] sendHeader = new byte[]{PROTOCOL_HEADER_FLAG, PROTOCOL_HEADER_FLAG, 0, 0, 0, 0, 0, 0};

        protected int id;
        protected byte[] buf;
        protected int off;
        protected int len;
        private WeakReference<IAnalysis> reference;

        public Sender(IAnalysis analysis, int id, byte[] buf, int off, int len) {
            super();
            this.id = id;
            this.buf = buf;
            this.off = off;
            this.len = len;
            reference = new WeakReference<IAnalysis>(analysis);
        }

        public int doSend() {
            IAnalysis iAnalysis = reference.get();
            if (iAnalysis == null) {
                return -1;
            }
            synchronized (sendHeader) {
                try {
                    sendHeader[2] = (byte) (this.len & 0xff);
                    sendHeader[3] = (byte) ((this.len & 0xff00) >> 8);
                    sendHeader[4] = (byte) (this.id & 0xff);
                    sendHeader[5] = (byte) ((this.id & 0xff00) >> 8);
                    sendHeader[6] = (byte) AOA_NO++;
                    sendHeader[7] = CrcUtils.crc8Table(sendHeader, 0, 7);
                    iAnalysis.selfWrite(sendHeader, 0, 8, false);
                    iAnalysis.selfWrite(this.buf, this.off, this.len, true);
                    return len;
                } catch (Exception e) {
                    iAnalysis.throwException(e, false);
                }
            }
            return -1;
        }

        @Override
        public void run() {
            doSend();
        }

        @Override
        public Integer call() throws Exception {
            return doSend();
        }
    }


    @Override
    public void setExceptionHandler(HandleException exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
    }

    @Override
    public void selfWrite(byte[] b, int off, int len, boolean flush) {
        if (isStart && outputStream != null) {
            try {
                this.outputStream.write(b, off, len);
                if (flush) {
                    this.outputStream.flush();
                }
            } catch (Exception e) {
                if (exceptionHandler != null) {
                    exceptionHandler.catchException(e);
                }
            }
        }

    }

    protected void run(Runnable runnable) {
        handlerService.execute(new R(this, runnable));
    }

    protected static class R implements Runnable {
        private Analysis a;
        private Runnable r;

        public R(Analysis a, Runnable r) {
            this.a = a;
            this.r = r;
        }

        @Override
        public void run() {
            while (a.isStart) {
                r.run();
            }
        }
    }

}
